#define PROCESS_NAME "CONTROLADOR_CENTRAL"

#include "ControladorCentral.h"
#include "../lib/Constantes.h"
#include "../lib/Logger.h"
#include "../lib/SignalHandler.h"
#include "../lib/Util.h"

void ControladorCentral::log(string message) {
	string name = PROCESS_NAME;
	string msg = name + ": " + message;
	logger->loggearLog(msg);
}

void ControladorCentral::sync(string message) {
	string name = PROCESS_NAME;
	string msg = name + ": " + message;
	logger->loggearSynchro(msg);
}

void ControladorCentral::debug(string message) {
	string name = PROCESS_NAME;
	string msg = name + ": " + message;
	logger->loggearDebug(msg);
}

ControladorCentral::ControladorCentral(int cantidadAscensores,
		int cantidadPisos, int capacidad, int velocidad, bool modoDebug,
		bool modoLog, bool modoSyn, bool modoCout) {

	Logger::getInstance()->setFlags(modoDebug, modoLog, modoSyn, modoCout);
	this->logger = Logger::getInstance();
	this->cantidadAscensores = cantidadAscensores;
	this->ascensoresRegistrados = 0;
	this->cantidadPisos = cantidadPisos;
	inicializarListaAscensores();
	this->monitorAscensores = new MonitorAscensores(cantidadAscensores,
			cantidadPisos, capacidad, velocidad);
	this->monitorPisos = new MonitorPisos(cantidadPisos);
	this->manejador = new ManejadorSemAscensores(cantidadAscensores);
	this->cola = new Cola<mensaje>((char*) CONTROLADOR_ARCHIVO,
			(char) CONTROLADOR_LETRA);

	/* Se registra el Handler*/
	SignalHandler::getInstance()->registrarHandler(SIGUSR1, &sigusr1_handler);
	this->debug("Controlador central instanciado.");
}

ControladorCentral::~ControladorCentral() {
	delete (monitorAscensores);
	delete (monitorPisos);
	delete (manejador);
	this->cola->eliminar();
	delete (cola);
	this->sync("Monitores, Manejadores y Cola destruidos.");
}

void ControladorCentral::inicializarListaAscensores() {

	for (int i = 0; i < MAXASCENSORES; i++) {
		this->listaAscensores[i].nroAscensor = -1;
		this->listaAscensores[i].pid = -1;
		this->listaAscensores[i].pisoActual = -1;
		this->listaAscensores[i].proximoPiso = -1;
	}
}

void ControladorCentral::registrarAscensor(tascensor ascensor) {
	this->debug(
			"Se registra nuevo ascensor numero "
					+ Util::intToString(ascensor.nroAscensor));

	this->listaAscensores[ascensor.nroAscensor].nroAscensor =
			ascensor.nroAscensor;
	this->listaAscensores[ascensor.nroAscensor].pid = ascensor.pid;
	this->listaAscensores[ascensor.nroAscensor].pisoActual =
			ascensor.pisoActual;
	this->listaAscensores[ascensor.nroAscensor].proximoPiso = ascensor.proxPiso;

	this->ascensoresRegistrados++;
	this->debug(
			"Total de ascensores registrados"
					+ Util::intToString(this->ascensoresRegistrados));
}

void ControladorCentral::eliminarAscensor(tascensor ascensor) {
	this->debug(
			"Se elimina ascensor numero "
					+ Util::intToString(ascensor.nroAscensor));

	this->listaAscensores[ascensor.nroAscensor].nroAscensor = -1;
	this->listaAscensores[ascensor.nroAscensor].pid = -1;
	this->listaAscensores[ascensor.nroAscensor].pisoActual = -1;
	this->listaAscensores[ascensor.nroAscensor].proximoPiso = -1;

	this->ascensoresRegistrados--;
	this->debug(
			"Total de ascensores registrados"
					+ Util::intToString(this->ascensoresRegistrados));
}

char ControladorCentral::obtenerDireccionAscensor(int pisoActual,
		int vect_pos[]) {
	// Si tengo gente adentro la dirección no se discute
	for (int i = (pisoActual - 1); i >= 0; i--) {
		if (vect_pos[i] == 1) {
			this->debug("Lleva gente bajando");
			return BAJANDO;
		}
	}
	for (int i = pisoActual + 1; i < this->cantidadPisos; i++) {
		if (vect_pos[i] == 1) {
			this->debug("Lleva gente subiendo");
			return SUBIENDO;
		}
	}
	this->debug("No lleva gente");
	return ESPERANDO;
}

bool ControladorCentral::buscarPisoSuperior(tascensor* pAscensor) {
	if (pAscensor->pisoActual < this->cantidadPisos - 1) {
		for (int i = pAscensor->pisoActual + 1; i < this->cantidadPisos; i++) {
			if (this->monitorPisos->leer(i, SUBIENDO) > 0) {
				// TODO ver si ya hay otro ascensor yendo para ahi
				pAscensor->proxPiso = i;
				pAscensor->estado = SUBIENDO;
				return true;
			}
		}
	}
	return false;
}

bool ControladorCentral::buscarBajandoDesdeTop(tascensor* pAscensor) {
	for (int i = this->cantidadPisos - 1; i > 0; i--) {
		if (this->monitorPisos->leer(i, BAJANDO) > 0) {
			// TODO ver si ya hay otro ascensor yendo para ahi
			pAscensor->proxPiso = i;
			if (i > pAscensor->pisoActual)
				pAscensor->estado = SUBIENDO;
			else
				pAscensor->estado = BAJANDO;
			return true;
		}
	}
	return false;
}

bool ControladorCentral::buscarPisoInferior(tascensor* pAscensor) {
	if (pAscensor->pisoActual > 0) {
		for (int i = pAscensor->pisoActual - 1; i > 0; i--) {
			if (this->monitorPisos->leer(i, BAJANDO) > 0) {
				// TODO ver si ya hay otro ascensor yendo para ahi
				pAscensor->proxPiso = i;
				pAscensor->estado = BAJANDO;
				return true;
			}
		}
	}
	return false;
}

bool ControladorCentral::buscarSubiendoDesdeBase(tascensor* pAscensor) {
	for (int i = 0; i < this->cantidadPisos; i++) {
		if (this->monitorPisos->leer(i, SUBIENDO) > 0) {
			// TODO ver si ya hay otro ascensor yendo para ahi
			pAscensor->proxPiso = i;
			if (i < pAscensor->pisoActual)
				pAscensor->estado = BAJANDO;
			else
				pAscensor->estado = SUBIENDO;
			return true;
		}
	}
	return false;
}

void ControladorCentral::subiendoParadaMasProxima(tascensor* pAscensor,
		int vect_pos[]) {
	this->debug("Tomo el primer piso en que se baja alguien del ascensor.");
	pAscensor->estado = SUBIENDO;
	for (int i = (pAscensor->pisoActual + 1); i < this->cantidadPisos; i++) {
		// Si alguien marco el piso ya hay que parar ahi
		if (vect_pos[i] == 1) {
			pAscensor->proxPiso = i;
			return;
		}
		// Sino veo si hay gente y no hay otro ascensor que pare ahi
		// TODO ver si ya hay otro ascensor yendo para ahi
		if (this->monitorPisos->leer(i, SUBIENDO) > 0) {
			pAscensor->proxPiso = i;
			return;
		}

	}
}

void ControladorCentral::bajandoParadaMasProxima(tascensor* pAscensor,
		int vect_pos[]) {
	this->debug("Tomo el primer piso en que se baja alguien del ascensor.");
	pAscensor->estado = BAJANDO;
	for (int i = (pAscensor->pisoActual - 1); i >= 0; i--) {
		// Si alguien marco el piso ya hay que parar ahi
		if (vect_pos[i] == 1) {
			pAscensor->proxPiso = i;
			return;
		}
		// Sino veo si hay gente y no hay otro ascensor que pare ahi
		// TODO ver si ya hay otro ascensor yendo para ahi
		if (this->monitorPisos->leer(i, BAJANDO) > 0) {
			pAscensor->proxPiso = i;
			return;
		}

	}
}

void ControladorCentral::encontrarDestino(tascensor* pAscensor) {
	if (pAscensor->estado == SUBIENDO) {
		if (!this->buscarPisoSuperior(pAscensor)) {
			if (!this->buscarBajandoDesdeTop(pAscensor)) {
				pAscensor->proxPiso = 0;
				if (pAscensor->pisoActual == 0)
					pAscensor->estado = ESPERANDO;
				else
					pAscensor->estado = BAJANDO;
			}
		}
	} else if (pAscensor->estado == BAJANDO) {
		if (!this->buscarPisoInferior(pAscensor)) {
			if (!this->buscarSubiendoDesdeBase(pAscensor)) {
				pAscensor->proxPiso = 0;
				if (pAscensor->pisoActual == 0)
					pAscensor->estado = ESPERANDO;
				else
					pAscensor->estado = BAJANDO;
			}
		}
	} else // ESPERANDO
	{
		if (!this->buscarSubiendoDesdeBase(pAscensor)) {
			if (!this->buscarBajandoDesdeTop(pAscensor)) {
				// ESTO ES REDUNDANTE, PERO PISO LOS DATOS CON LOS MISMOS VALORES
				pAscensor->proxPiso = 0;
				pAscensor->estado = ESPERANDO;
			}
		}
	}
}

void ControladorCentral::calcularProximoPiso(tascensor ascensor,
		int vect_pos[]) {
	this->debug(
			"Calculando el proximo piso para el ascensor "
					+ Util::intToString(ascensor.nroAscensor));
	this->personasEnPisos = 0;
	char estadoInterno = this->obtenerDireccionAscensor(ascensor.pisoActual,
			vect_pos);
	if (estadoInterno == SUBIENDO) {
		this->subiendoParadaMasProxima(&ascensor, vect_pos);
	} else if (estadoInterno == BAJANDO) {
		this->bajandoParadaMasProxima(&ascensor, vect_pos);
	} else {
		this->encontrarDestino(&ascensor);
	}
	if ((ascensor.estado == ESPERANDO)
			&& (!sigusr1_handler.estaGenerandoPersonas())) {
		this->eliminarAscensor(ascensor);
		return;
	}
	this->debug(
			"Grabamos al Ascensor " + Util::intToString(ascensor.nroAscensor)
					+ " que se dirija al piso "
					+ Util::intToString(ascensor.proxPiso));

	this->listaAscensores[ascensor.nroAscensor].pisoActual =
			ascensor.pisoActual;
	this->listaAscensores[ascensor.nroAscensor].proximoPiso = ascensor.proxPiso;
	this->listaAscensores[ascensor.nroAscensor].estado = ascensor.estado;
}

void ControladorCentral::procesarPeticion() {
	this->debug(
			"Se procesa la nueva peticion del ascensor "
					+ Util::intToString(this->peticionRecibida.nroAscensor));
	tascensor ascensor = this->monitorAscensores->leer(
			this->peticionRecibida.nroAscensor);

	int vect_pos[MAXPISOS];
	this->monitorAscensores->leerVector(ascensor.nroAscensor, vect_pos);

	if (this->listaAscensores[ascensor.nroAscensor].nroAscensor == -1)
		this->registrarAscensor(ascensor);

	this->calcularProximoPiso(ascensor, vect_pos);
	this->log(
			"Se asigna el piso "
					+ Util::intToString(
							this->listaAscensores[ascensor.nroAscensor].proximoPiso)
					+ " al ascensor "
					+ Util::intToString(this->peticionRecibida.nroAscensor));
	this->monitorAscensores->asignarProximoPiso(
			this->peticionRecibida.nroAscensor,
			this->listaAscensores[ascensor.nroAscensor].proximoPiso,
			this->listaAscensores[ascensor.nroAscensor].estado);
	this->sync(
			"Se avisa al ascensor "
					+ Util::intToString(this->peticionRecibida.nroAscensor)
					+ " que ya puede seguir su ejecución.");
	this->manejador->despertarAscensor(this->peticionRecibida.nroAscensor);
	this->log("Peticion procesada");
}

void ControladorCentral::recibirPeticion() {
	this->debug("Recibo una petición.");
	this->peticionRecibida.pid = -1;
	this->peticionRecibida.nroAscensor = -1;
	this->sync("Me fijo en la cola si llega una petición.");
	this->cola->leer(0, &(this->peticionRecibida));
}

void ControladorCentral::start() {
	this->sync("Comienzo a recibir peticiones de ascensores.");
	while ((sigusr1_handler.estaGenerandoPersonas())
			|| (ascensoresRegistrados > 0)) {
		this->recibirPeticion();

		/* Dado que puede ejecutarse el handler en
		 * el medio, se chequea que el valor haya sido modificado
		 */
		if (this->peticionRecibida.nroAscensor == -1)
			continue;

		this->procesarPeticion();
	}
	this->log(
			"Se acabaron las personas y no se generan más. Fin de la ejecución.");
}
